export * from "./Mfs";
import { isObj, u8a2s, meval, strParse, type, isNumber, Mfs, next } from "./Mfs";
import { md5 } from "js-md5";
import { inflate } from "pako/lib/inflate";
function closeUni() {
  return new Promise<any>((resolve) => {
    uni.closeSocket({ success: resolve, fail: resolve });
  });
}
/** web与uni通用的websocket */
export class WS {
  _private = {
    url: "",
    onmessage: ((p) => { return p; }) as cbk,
    onclose: undefined as undefined | cbk,
    onopen: undefined as undefined | cbk,
    onerror: undefined as undefined | cbk,
    ws: undefined as any as WebSocket,
    onmessaged: ((p) => { return p; }) as cbk,
    liveTime: 10000,
  };
  /** 数据缓存时间(毫秒 默认1000) 当发送相同请求时,不向服务器发送数据,而是直接由缓存返回 */
  public cacheTime = 1000;

  public isConnect = false;
  private isConnecting = false;
  private readonly sendData: (data: MWS.CacheSend) => void;
  private readonly _send: (data: string) => void;
  public readonly mfs = new Mfs();

  readonly isUni: boolean = false;
  private readonly cacheSend: { [dataHash: string]: MWS.CacheSend; } = {};
  public get now() { return new Date().getTime(); }
  public get nextCacheTime() { return new Date().getTime() + this.cacheTime; }
  private getCache(sid: string) {
    if (!this.cacheSend[sid]) { this.cacheSend[sid] = { cbks: [], t: 1, r: false, s: true }; }
    const r = this.cacheSend[sid];
    r.t = this.nextCacheTime;
    return r;
  }
  /** 预拉取数据 */
  private preBefore(data: MWS.CacheSend) {
    return new Promise<any>((resolve) => {
      const send: obj = strParse(data.send),
        c = this.getCache(Object.assign(send, { sid: "_" + send.sid }).sid);
      if (!/\w+Read$/.test(send.type)) {
        return resolve({});
      }
      if (c.recv) {
        resolve(c.recv);
      } else {
        c.cbks.push(resolve);
        if (!c.send) {
          c.send = JSON.stringify(send);
        }
        this.sendData(c);
      }
    });
  }
  private async pre(data: MWS.CacheSend) {
    const r = await this.preBefore(data);
    if (typeof r.data === "string") {
      const d = this.mfs.get(r.sid.substring(1));
      if (d) {
        if (d.md5 == r.data) {
          d.createTime = r.createTime;
          this.onmessage({ data: d });
          return true;
        } else {
          this.mfs.del(d.sid);
        }
      }
    }
  }
  /** 心跳 */
  private live = {
    h: 0,
    index: 0,
    destroy: () => {
      if (this.live.h) {
        clearInterval(this.live.h);
        this.live.h = 0;
      }
      this.live.index = 0;
    },
    open: () => {
      this.live.destroy();
      this.live.h = setInterval(() => {
        this._send(JSON.stringify({ type: "live", data: { time: new Date().getTime(), index: ++this.live.index } }));
      }, this._private.liveTime);
    }
  };


  /** 发送数据 */
  public send(data?: any) {
    if (!data || typeof data != "object") { data = {}; }
    return new Promise<any>(async (resolve) => {
      const pd: obj = WS.delProps(strParse(data)),
        sid = md5(JSON.stringify(pd));
      while (!this.isConnect) {
        this.connect();
        await next();
      }
      const c = this.getCache(sid);
      if (c.recv) {
        resolve(c.recv);
      } else {
        c.cbks.push(resolve);
        if (!c.send) {
          c.send = JSON.stringify(Object.assign(pd, { sid }));
          if (await this.pre(c)) { return; }
        }
        this.sendData(c);
      }
    });
  }
  /** 与send一致 */
  public sendSync(data?: any) {
    return this.send(data);
  }
  /** 可用作初始化,重新连接等操作 */
  init(wsConfig?: WSConfig) {
    if (wsConfig
      && wsConfig.onmessage instanceof Function
      && typeof wsConfig.url === "string" && wsConfig.url.length > 8 && !wsConfig.url.indexOf("ws")
    ) {
      this.close();
      // @ts-ignore
      if (!(wsConfig.liveTime >= 1000)) {
        wsConfig.liveTime = 10000;
      }
      this._private.liveTime = wsConfig.liveTime as number;

      // const url = this._private.url;
      this._private.url = wsConfig.url;
      if (typeof wsConfig.token === "string") {
        this._private.url += (/\/$/.test(this._private.url) ? "" : "/") + encodeURIComponent(encodeURIComponent(wsConfig.token));
      }
      if (wsConfig.onclose) { this._private.onclose = wsConfig.onclose; }
      if (wsConfig.onmessage) { this._private.onmessage = wsConfig.onmessage; }
      if (wsConfig.onmessaged) { this._private.onmessaged = wsConfig.onmessaged; }
      if (wsConfig.onopen) { this._private.onopen = wsConfig.onopen; }
      if (wsConfig.onerror) { this._private.onerror = wsConfig.onerror; }
      if (this.isConnect) { this.onclose(new Event("重新连接")); }
      this.cacheTime = isNumber(wsConfig.cacheTime) && wsConfig.cacheTime >= 0 ? wsConfig.cacheTime : 1000;
      this.connect();
    }
  }

  constructor (wsConfig?: WSConfig) {
    try { this.isUni = !!uni; } catch (_) { this.isUni = false; }
    setInterval(() => {
      const n = this.now;
      Object.keys(this.cacheSend).forEach(k => {
        const c = this.cacheSend[k];
        if (c.r) {
          if (c.t < n) {
            c.cbks.forEach(e => { try { e(c.recv); } catch (_) { } });
            delete this.cacheSend[k];
          } else if (c.cbks.length) {
            c.cbks.forEach(e => { try { e(c.recv); } catch (_) { } });
            c.cbks.splice(0);
          }
        }
      });
    }, 100);
    if (this.isUni) {
      this._send = (data: string) => uni.sendSocketMessage({ data });
    } else {
      this._send = (data: string) => this._private.ws.send(data);
    }
    this.sendData = (data) => {
      if (this.isConnect) {
        if (data.s && typeof data.send != "undefined") {
          data.s = false;
          this._send(data.send);
        }
      } else {
        this.connect();
      }
    };
    this.init(wsConfig);
  }
  /** 用于格式化传入的url */
  public static FormatUrl(url?: string) {
    let ret = url || "";
    try {
      if ((typeof url != "string" || url.length < 8) && meval("location")) {
        ret = `ws${meval("location.origin.substring(4)")}/${url || ''}`;
      }
      if (ret) {
        if (ret.length - 1 != ret.lastIndexOf("/")) {
          ret += "/";
        }
        if (!ret.indexOf("http")) {
          ret = "ws" + ret.substring(4);
        }
      }
    } catch (error) { }
    return ret;
  }
  /** 删除以_开头的属性,避免过多的数据传入服务器造成的压力 */
  public static delProps<T>(p: T, _exid?: boolean) {
    if (isObj(p)) {
      const ret: obj = {};
      Object.keys(p).sort().forEach(k => {
        const e = p[k];
        if (k.indexOf("_") && type(e) !== "undefined") {
          if (_exid && k.indexOf("#") && type(e.id) !== "undefined") {
            ret[k] = e.id;
          } else if (e instanceof Date) {
            ret[k] = parseInt(e.getTime() / 1000);
          } else if (e instanceof Uint8Array) {
            ret[k] = u8a2s(e);
          } else {
            ret[k] = WS.delProps(e, true);
          }
        }
      });
      p = ret as any;
    } else if (Array.isArray(p)) {
      p = p.map(e => WS.delProps(e, true)) as any;
    }
    return p;
  }
  /** 清空所有未返回的回调,所有之前需要有回调的引用将不再返回 */
  public clear() {
    Object.keys(this.cacheSend).forEach(k => {
      delete this.cacheSend[k];
    });
  }

  private async onmessage(p: any) {
    if (p.data instanceof ArrayBuffer) {
      const d = new Uint8Array(p.data), sy = Symbol();
      try { p.data = (new TextDecoder("utf-8")).decode(d); } catch (_) { console.error(_); }
      if (strParse(p.data, sy) == sy) {
        p.data = inflate(d, { to: "string" });
      }
    }
    const data: { sid: string; data: any; md5?: String; } = strParse(p.data, { sid: "", data: {} });
    let rd: obj = strParse(p.data, { sid: "", data: {} });
    if (rd.message && rd.type === "live" && rd.message === "type错误:live") {
      return;
    }
    try { rd = (await this._private.onmessage(rd)); } catch (_) { console.error("错误", _); }
    if (data.sid) {
      const c = this.cacheSend[data.sid];
      if (c) {
        c.recv = rd;
        c.r = true;
        c.t = this.nextCacheTime;
        if (`${data.sid}`.indexOf("_")) {
          data.md5 && this.mfs.set(data.sid, data);
          c.cbks.forEach((e) => { try { e(rd); } catch (_) { } });
        } else {
          for (const e of c.cbks) {
            try { await e(data); } catch (_) { }
          }
        }
        c.cbks.splice(0);
      }
    }
    try { this._private.onmessaged(data); } catch (_) { console.error("onmessaged错误", _); }
  }
  private onclose(...p: any) {
    this.live.destroy();
    Object.keys(this.cacheSend).forEach(k => {
      this.cacheSend[k].s = true;
    });
    this.isConnecting = this.isConnect = false;
    if (this.isUni) { closeUni(); } else { this._private.ws = undefined as any; }
    return this._private.onclose instanceof Function && this._private.onclose(...p);
  }
  private onopen(...p: any) {
    this.live.open();
    this.isConnect = true;
    this.isConnecting = false;
    Object.keys(this.cacheSend).forEach(k => {
      const c = this.cacheSend[k];
      if (c.recv) {
        c.t = this.nextCacheTime;
        c.cbks.forEach((e) => { try { e(c.recv); } catch (_) { } });
        c.cbks.splice(0);
      } else if (c.send) {
        this.sendData(c);
      } else {
        c.cbks.forEach((e) => { try { e(); } catch (_) { } });
        delete this.cacheSend[k];
      }
    });
    return this._private.onopen instanceof Function && this._private.onopen(...p);
  }
  private onerror(...p: any) {
    this.isConnecting = this.isConnect = false;
    if (this.isUni) { uni.closeSocket({}); } else { this._private.ws = undefined as any; }
    return this._private.onerror instanceof Function && this._private.onerror(...p);
  }
  public connect() {
    if (!this.isConnecting && !this.isConnect && this._private.url) {
      this.isConnecting = true;
      if (this.isUni) {
        uni.connectSocket({ url: this._private.url, });
        uni.onSocketOpen(this.onopen.bind(this));
        uni.onSocketError(this.onerror.bind(this));
        uni.onSocketClose(this.onclose.bind(this));
        uni.onSocketMessage(this.onmessage.bind(this));
      } else {
        this._private.ws = new WebSocket(this._private.url);
        this._private.ws.onopen = this.onopen.bind(this);
        this._private.ws.onerror = this.onerror.bind(this);
        this._private.ws.onclose = this.onclose.bind(this);
        this._private.ws.onmessage = this.onmessage.bind(this);
      }
    }
  }
  /** 关闭并清除连接 */
  public close(clearUrl?: any) {
    this.live.destroy();
    if (clearUrl) {
      this._private.url = "";
    }
    this.isConnecting = this.isConnect = false;
    if (this.isUni) {
      closeUni();
    } else {
      this._private.ws?.close();
    }
    Object.keys(this._private).forEach(k => {
      delete this._private[k];
    });
    this.clear();
  }
}